一人公司技术基建:Next.js 迁移 Cloudflare Workers 的 5 个致命暗坑

一人公司技术基建:Next.js 迁移 Cloudflare Workers 的 5 个致命暗坑

本来以为只是一条部署命令的事,结果我硬生生修了两天的 Bug。

在做一人公司的技术选型时,我一直信奉一个铁律:最简单的解决方案胜出

过去,我们习惯用 @cloudflare/next-on-pages 一键把 Next.js 扔到 Cloudflare Pages 上。但随着业务变复杂,我们需要完整的 Node.js 生态,加上 D1 数据库和 R2 存储的深度介入,Pages 模式的局限性就彻底暴露了。

官方目前主推的架构是:Next.js + OpenNext + Cloudflare Workers

从 Pages 迁移到 Workers,这绝对不是换一条命令那么简单,而是底层“疆域”的彻底重构。在这场基建升级中,我结结实实地踩了 5 个致命暗坑。

这篇实录,希望能帮你省下整整两天的调试时间。

坑一:被 3MB 体积限制逼出的“过度工程”

现象: 部署时频繁遇到 3MB 脚本体积超限报错。我的第一反应是花大量时间去精简代码、调整打包配置、剔除依赖。
本质: 陷入了执行细节,违背了效率原则。在 Cloudflare 的生态里,3MB 是免费版给“玩具项目”设的门槛。为了绕过它而写的各种 trick,最后都会变成你的技术债。
破局: 用物理手段消除物理障碍。直接订阅 5 美元/月的 Workers 付费计划。这不仅把体积上限解锁到了 10MB+,还打包了充足的 D1 读写和 R2 请求额度。能用 5 美元买到的基建效率,绝不用过度工程去换取。

坑二:“刻舟求剑”的路由报错 (404 噩梦)

现象:wrangler.toml 里配置了 OpenNext 的输出目录 .open-next/assets,但在 Cloudflare Dashboard 里部署后,访问页面始终 404。
本质: 两套技术体系的错位。@cloudflare/next-on-pages 生成的是纯静态目录,而 @opennextjs/cloudflare 生成的是一个处理动态路由的 worker.js 和一堆静态资产。如果你还试图把项目挂在 Cloudflare Pages 下,系统根本不会去执行你的 Worker 逻辑。
破局: 果断废弃旧的 Pages 项目。在控制台新建一个 Worker 项目,并在 wrangler.toml 中使用标准的 Workers 格式配置 main = ".open-next/worker.js"[assets]。认清当前所处的架构疆域,丢掉旧地图。

坑三:被时代抛弃的 export const runtime = 'edge'

现象: 部署时出现大量匪夷所思的 Could not resolve "./cloudflare/images.js" 路径报错。
本质: 这是旧时代留下的紧箍咒。在 Pages 时代,写上 runtime = 'edge' 是为了迎合 Vercel/Cloudflare 的边缘运行时限制。但在 OpenNext 架构下,工具链会自动处理所有环境适配,并提供 Node.js 兼容性。保留这行代码,反而会干扰 Next.js 的正常编译。
破局: 全局搜索并彻底删除所有文件中的 export const runtime = 'edge'。解开封印,拥抱完整的 API 能力。

坑四:Next.js SSG 预渲染遭遇“空数据库”阻击

现象: 本地或 CI 执行 next build 时,终端直接崩溃,报错 no such table: site_settings: SQLITE_ERROR
本质: Next.js 极其执着于性能优化,它会在 next build 阶段启动一个“幽灵访客”去预渲染静态页面 (SSG)。如果你的页面里写了读取 D1 数据库的代码,它在此刻就会发起真实查询。由于构建时缺乏环境标识,它默认连上了尚未建表的生产库,当场阵亡。
破局: 实施防御性编程与上下文收敛。

  1. 废弃旧的 request.env?.DB 取法。
  2. 建立单一事实来源 src/lib/db/get-db.ts,统一通过 await getCloudflareContext({ async: true }) 获取绑定。
  3. 增加兜底逻辑:在构建期(或读取失败时),允许程序使用默认 JSON 数据返回,确保构建流水线能顺利跑完。
// 防御性编程示例:统一封装 DB 获取
import { getCloudflareContext } from '@opennextjs/cloudflare';

export async function getDB() {
  try {
    const { env } = await getCloudflareContext({ async: true });
    return env.DB;
  } catch (error) {
    console.warn("DB Context not available, likely during next build SSG.");
    return null; 
  }
}

坑五:迷信自动化流水线导致“测试库失联”

现象: 明明推送的是 dev 分支,wrangler.toml 也配置了 [env.preview],但部署后依然去查了正式环境的数据库。
本质: Cloudflare Workers 的原生 Git 自动化集成极其机械,它不具备 Pages 那种聪明的“分支路由”能力。无论推送什么分支,它只会无脑执行 Dashboard 里写死的那句默认命令,永远读取生产配置。
破局: 停止期待工具的魔法。全面接管 CI/CD,切断 Cloudflare 的自动构建,引入 GitHub Actions。在流水线中明确定义输入与输出:如果是 main 分支,执行 npx wrangler deploy;如果是其他分支,显式注入 npx wrangler deploy --env preview


总结与行动指南

架构迁移带来的阵痛是真实的,但每一次报错的解决,都在加固这套底层基建的护城河。

摆脱了 Pages 的束缚,我们现在拥有了完整的 Node.js 兼容性、真正的 D1/R2 边缘调用能力,以及一套随时可以复用到下一个微型项目的高效脚手架。代码是负债,但坚实的基础设施是资产。

💡 送给你的最小行动指南 (Call to Action)
如果你正在或者准备将 Next.js 项目迁移到 OpenNext,不要先急着看文档。现在就打开你的代码库,全局搜索 export const runtime = 'edge'把它删干净。相信我,仅仅这一个动作,就能帮你避开迁移路上至少一半的玄学报错。